Trend Following System - Apple Stock

Calculate slow and fast exponential moving averages for AAPL stock using historical data, visualize results and calculate return and performance metrics. Our strategy purchases the stock as the fast exponential moving average crosses above the slow moving average. This system does not go short.

Alex Labuda (Suny New Paltz School of Business - Analytics Capstone)
9/9/2021

Download AAPL Stock Data

getSymbols.warning4.0=FALSE

getSymbols("AAPL", src='yahoo', )
[1] "AAPL"
getSymbols("^GSPC", src='yahoo',)
[1] "^GSPC"
# AAPL
df <- data.frame(Date=index(AAPL),coredata(AAPL))
# take the last 3000 observations
df <- tail(df, 4000)
# reset index
rownames(df) = NULL

#SPX
SPX <- data.frame(Date=index(GSPC),coredata(GSPC))

SPX <- tail(SPX, 4000)
# reset index
rownames(SPX) = NULL
# copy the data
raw_data = df
head(raw_data)
        Date AAPL.Open AAPL.High AAPL.Low AAPL.Close AAPL.Volume
1 2007-01-03  3.081786  3.092143 2.925000   2.992857  1238319600
2 2007-01-04  3.001786  3.069643 2.993571   3.059286   847260400
3 2007-01-05  3.063214  3.078571 3.014286   3.037500   834741600
4 2007-01-08  3.070000  3.090357 3.045714   3.052500   797106800
5 2007-01-09  3.087500  3.320714 3.041071   3.306071  3349298400
6 2007-01-10  3.383929  3.492857 3.337500   3.464286  2952880000
  AAPL.Adjusted
1      2.569716
2      2.626753
3      2.608048
4      2.620926
5      2.838647
6      2.974493
summary(raw_data)
      Date              AAPL.Open         AAPL.High      
 Min.   :2007-01-03   Min.   :  2.835   Min.   :  2.929  
 1st Qu.:2010-09-08   1st Qu.:  9.675   1st Qu.:  9.754  
 Median :2014-05-17   Median : 23.250   Median : 23.456  
 Mean   :2014-05-17   Mean   : 33.008   Mean   : 33.351  
 3rd Qu.:2018-01-23   3rd Qu.: 41.858   3rd Qu.: 42.481  
 Max.   :2021-09-29   Max.   :156.980   Max.   :157.260  
    AAPL.Low         AAPL.Close       AAPL.Volume       
 Min.   :  2.793   Min.   :  2.793   Min.   :4.545e+07  
 1st Qu.:  9.577   1st Qu.:  9.667   1st Qu.:1.276e+08  
 Median : 23.025   Median : 23.290   Median :2.675e+08  
 Mean   : 32.657   Mean   : 33.017   Mean   :4.006e+08  
 3rd Qu.: 41.369   3rd Qu.: 41.956   3rd Qu.:5.476e+08  
 Max.   :154.390   Max.   :156.690   Max.   :3.373e+09  
 AAPL.Adjusted    
 Min.   :  2.398  
 1st Qu.:  8.300  
 Median : 20.635  
 Mean   : 31.511  
 3rd Qu.: 40.345  
 Max.   :156.690  
# rename AAPL columns to simplicity
names(raw_data)[names(raw_data)=="AAPL.Open"] = "Open"
names(raw_data)[names(raw_data)=="AAPL.High"] = "High"
names(raw_data)[names(raw_data)=="AAPL.Low"] = "Low"
names(raw_data)[names(raw_data)=="AAPL.Close"] = "Close"
names(raw_data)[names(raw_data)=="AAPL.Volume"] = "Volume"
names(raw_data)[names(raw_data)=="AAPL.Adjusted"] = "Price"

# rename SPX columns to simplicity
names(SPX)[names(SPX)=="GSPC.Open"] = "Open"
names(SPX)[names(SPX)=="GSPC.High"] = "High"
names(SPX)[names(SPX)=="GSPC.Low"] = "Low"
names(SPX)[names(SPX)=="GSPC.Close"] = "Close"
names(SPX)[names(SPX)=="GSPC.Volume"] = "Volume"
names(SPX)[names(SPX)=="GSPC.Adjusted"] = "Price"
head(raw_data)
        Date     Open     High      Low    Close     Volume    Price
1 2007-01-03 3.081786 3.092143 2.925000 2.992857 1238319600 2.569716
2 2007-01-04 3.001786 3.069643 2.993571 3.059286  847260400 2.626753
3 2007-01-05 3.063214 3.078571 3.014286 3.037500  834741600 2.608048
4 2007-01-08 3.070000 3.090357 3.045714 3.052500  797106800 2.620926
5 2007-01-09 3.087500 3.320714 3.041071 3.306071 3349298400 2.838647
6 2007-01-10 3.383929 3.492857 3.337500 3.464286 2952880000 2.974493
head(SPX)
        Date    Open    High     Low   Close     Volume   Price
1 2007-01-03 1418.03 1429.42 1407.86 1416.60 3429160000 1416.60
2 2007-01-04 1416.60 1421.84 1408.43 1418.34 3004460000 1418.34
3 2007-01-05 1418.34 1418.34 1405.75 1409.71 2919400000 1409.71
4 2007-01-08 1409.26 1414.98 1403.97 1412.84 2763340000 1412.84
5 2007-01-09 1412.84 1415.61 1405.42 1412.11 3038380000 1412.11
6 2007-01-10 1408.70 1415.99 1405.32 1414.85 2764660000 1414.85

Calculate Daily Return

Calculate a simple day-to-day return, from adjusted-close to adjusted-close

# AAPL
raw_data$return = NA
raw_data$log_return = NA

for(t in 2:nrow(raw_data)){
  raw_data$return[t] = (raw_data$Price[t] - raw_data$Price[t-1])/ raw_data$Price[t-1]
  raw_data$log_return[t] = log(raw_data$Price[t]/raw_data$Price[t-1])
}

head(raw_data)
        Date     Open     High      Low    Close     Volume    Price
1 2007-01-03 3.081786 3.092143 2.925000 2.992857 1238319600 2.569716
2 2007-01-04 3.001786 3.069643 2.993571 3.059286  847260400 2.626753
3 2007-01-05 3.063214 3.078571 3.014286 3.037500  834741600 2.608048
4 2007-01-08 3.070000 3.090357 3.045714 3.052500  797106800 2.620926
5 2007-01-09 3.087500 3.320714 3.041071 3.306071 3349298400 2.838647
6 2007-01-10 3.383929 3.492857 3.337500 3.464286 2952880000 2.974493
        return   log_return
1           NA           NA
2  0.022195838  0.021953096
3 -0.007120959 -0.007146434
4  0.004937793  0.004925642
5  0.083070258  0.079799840
6  0.047855898  0.046746074
# SPX
SPX$return = NA
SPX$log_return = NA

for(t in 2:nrow(SPX)){
  SPX$return[t] = (SPX$Price[t] - SPX$Price[t-1])/ SPX$Price[t-1]
  SPX$log_return[t] = log(SPX$Price[t]/SPX$Price[t-1])
}

head(SPX)
        Date    Open    High     Low   Close     Volume   Price
1 2007-01-03 1418.03 1429.42 1407.86 1416.60 3429160000 1416.60
2 2007-01-04 1416.60 1421.84 1408.43 1418.34 3004460000 1418.34
3 2007-01-05 1418.34 1418.34 1405.75 1409.71 2919400000 1409.71
4 2007-01-08 1409.26 1414.98 1403.97 1412.84 2763340000 1412.84
5 2007-01-09 1412.84 1415.61 1405.42 1412.11 3038380000 1412.11
6 2007-01-10 1408.70 1415.99 1405.32 1414.85 2764660000 1414.85
         return    log_return
1            NA            NA
2  0.0012282861  0.0012275323
3 -0.0060845814 -0.0061031679
4  0.0022203184  0.0022178572
5 -0.0005166764 -0.0005168099
6  0.0019403524  0.0019384723

Indicators and Studies

Calculate Slow and Fast Moving Averages

Here we calculate a 60-day moving average (slow) as well as a 30-day moving average (fast) to use as our study

Two_MovAvg_function = function(variable, slow_period = 100, fast_period = 20){
  fast_ma = rep(NA, length(variable))
  slow_ma = rep(NA, length(variable))
  
  # MA's
  for (t in (slow_period+1):length(variable)){
    est_slow = mean(variable[(t-(slow_period+1)):(t-1)])
    est_fast = mean(variable[(t-(fast_period+1)):(t-1)])
    
    fast_ma[t] = est_fast
    slow_ma[t] = est_slow
  }
  
  ma_data = data.frame(fast_ma = fast_ma,
                       slow_ma  = slow_ma)
  return(ma_data)
}

Calculate Exponential MA for MACD

EA_Mov_Avg = function(variable, slow_lag = 100, fast_lag = 25){
  fast_ea = variable
  slow_ea = variable
  ema_diff = rep(NA, length(variable))
  
  # EMAs
  for (t in 2:length(variable)){ 
      est_slow = slow_ea[t-1] + (slow_ea[t] - slow_ea[t-1]) / ((slow_lag + 1) / 2)
      est_fast = fast_ea[t-1] + (fast_ea[t] - fast_ea[t-1]) / ((fast_lag + 1) / 2)
      
      ema_diff[t] = est_slow - est_fast
      
    slow_ea[t] = est_slow
    fast_ea[t] = est_fast
  }
  
  ema_data = data.frame(fast_ema = fast_ea,
                        slow_ema = slow_ea,
                        ema_diff = ema_diff)
  return(ema_data)
}
# save these as new columns back to original data 

new_df = EA_Mov_Avg(raw_data$Close, slow_lag = 150, fast_lag = 30)

raw_data$slow_ma = new_df$slow_ema
raw_data$fast_ma = new_df$fast_ema
raw_data$ema_diff = new_df$ema_diff

Average True Range

True Range

max_range_functon = function(variable){
  max_range = rep(0, length(variable))
  
  for (t in 2:length(raw_data$Price)){
    max_rng = max(abs(raw_data$High[t] - raw_data$Low[t]), abs(raw_data$High[t] - raw_data$Close[t-1]), abs(raw_data$Close[t-1] - raw_data$Low[t]))
    
    max_range[t] = max_rng
  }
  max_range_data = data.frame(max_range = max_range)
  
  return(max_range_data)
}

# Test Function
# max_range_functon(raw_data$Close[1:10])

# Implement on full dataset
max_range_df = max_range_functon(raw_data$Close)

# Create new variable in data
raw_data$max_range = max_range_df$max_range
head(raw_data)
        Date     Open     High      Low    Close     Volume    Price
1 2007-01-03 3.081786 3.092143 2.925000 2.992857 1238319600 2.569716
2 2007-01-04 3.001786 3.069643 2.993571 3.059286  847260400 2.626753
3 2007-01-05 3.063214 3.078571 3.014286 3.037500  834741600 2.608048
4 2007-01-08 3.070000 3.090357 3.045714 3.052500  797106800 2.620926
5 2007-01-09 3.087500 3.320714 3.041071 3.306071 3349298400 2.838647
6 2007-01-10 3.383929 3.492857 3.337500 3.464286 2952880000 2.974493
        return   log_return  slow_ma  fast_ma     ema_diff max_range
1           NA           NA 2.992857 2.992857           NA  0.000000
2  0.022195838  0.021953096 2.993737 2.997143 -0.003405888  0.076786
3 -0.007120959 -0.007146434 2.994316 2.999746 -0.005429937  0.064285
4  0.004937793  0.004925642 2.995087 3.003150 -0.008062751  0.052857
5  0.083070258  0.079799840 2.999206 3.022693 -0.023487057  0.279643
6  0.047855898  0.046746074 3.005366 3.051183 -0.045816917  0.186786

Average True Range Function

ATR_function = function(variable, lag = 15){
  atr_list = rep(NA, length(variable))
  
  # MA's
  for (t in (lag+2):length(variable)){
    est_atr = mean(variable[(t-(lag)):t])
    
    atr_list[t] = est_atr
  }
  
  atr_data = data.frame(atr = atr_list)
  return(atr_data)
}

atr_data = ATR_function(raw_data$max_range)
raw_data$atr = atr_data$atr

Risk per lot

risk_multiplier = 5
equity = 100000


raw_data$risk_per_lot = raw_data$atr * risk_multiplier
head(raw_data) 
        Date     Open     High      Low    Close     Volume    Price
1 2007-01-03 3.081786 3.092143 2.925000 2.992857 1238319600 2.569716
2 2007-01-04 3.001786 3.069643 2.993571 3.059286  847260400 2.626753
3 2007-01-05 3.063214 3.078571 3.014286 3.037500  834741600 2.608048
4 2007-01-08 3.070000 3.090357 3.045714 3.052500  797106800 2.620926
5 2007-01-09 3.087500 3.320714 3.041071 3.306071 3349298400 2.838647
6 2007-01-10 3.383929 3.492857 3.337500 3.464286 2952880000 2.974493
        return   log_return  slow_ma  fast_ma     ema_diff max_range
1           NA           NA 2.992857 2.992857           NA  0.000000
2  0.022195838  0.021953096 2.993737 2.997143 -0.003405888  0.076786
3 -0.007120959 -0.007146434 2.994316 2.999746 -0.005429937  0.064285
4  0.004937793  0.004925642 2.995087 3.003150 -0.008062751  0.052857
5  0.083070258  0.079799840 2.999206 3.022693 -0.023487057  0.279643
6  0.047855898  0.046746074 3.005366 3.051183 -0.045816917  0.186786
  atr risk_per_lot
1  NA           NA
2  NA           NA
3  NA           NA
4  NA           NA
5  NA           NA
6  NA           NA
# store cross over dates for charting annotations

h = raw_data[which((raw_data$fast_ma > raw_data$slow_ma) & (raw_data$fast_ma[-1] < raw_data$slow_ma[-1]))+1,]
l = raw_data[which((raw_data$fast_ma < raw_data$slow_ma) & (raw_data$fast_ma[-1] > raw_data$slow_ma[-1]))+1,]
h = na.exclude(h)
h
           Date     Open      High       Low     Close     Volume
276  2008-02-06  4.67250  4.711429  4.348929  4.357143 1573272400
429  2008-09-15  5.07250  5.274643  5.012857  5.012857  920634400
1479 2012-11-14 19.48214 19.551786 19.149286 19.174286  477170400
2168 2015-08-12 28.13250 28.855000 27.407499 28.809999  404870000
2997 2018-11-26 43.56000 43.737499 42.564999 43.654999  179994000
3336 2020-04-02 60.08500 61.287498 59.224998 61.232498  165934000
         Price      return  log_return   slow_ma  fast_ma   ema_diff
276   3.741113 -0.05689587 -0.05857858  5.524325  5.47893 0.04539427
429   4.304120 -0.05760781 -0.05933375  5.865623  5.82371 0.04191335
1479 16.609562 -0.01108831 -0.01115024 21.400512 21.37701 0.02350558
2168 26.453585  0.01541974  0.01530206 30.470711 30.39591 0.07480340
2997 42.441143  0.01352380  0.01343317 50.173922 49.85595 0.31796977
3336 60.567722  0.01668669  0.01654900 66.242964 66.13575 0.10721376
     max_range       atr risk_per_lot
276   0.362500 0.3355802     1.677901
429   0.306429 0.2209376     1.104688
1479  0.402500 0.6442408     3.221204
2168  1.447501 0.8901563     4.450782
2997  1.172500 1.7617186     8.808593
3336  2.062500 4.4723434    22.361717
l
           Date      Open      High       Low     Close     Volume
327  2008-04-21  5.793214  6.017857  5.777143  6.005714 1039152800
576  2009-04-16  4.256786  4.398214  4.242500  4.337500  593446000
1670 2013-08-20 18.203930 18.234644 17.886429 17.895357  358688400
2419 2016-08-10 27.177500 27.225000 26.940001 27.000000   96034000
3081 2019-03-29 47.457500 47.520000 47.134998 47.487499   94256000
3343 2020-04-14 70.000000 72.062500 69.512497 71.762497  194994800
         Price       return   log_return   slow_ma   fast_ma
327   5.156605  0.044212429  0.043262945  5.224567  5.236748
576   3.724247  0.032386626  0.031873234  3.858784  3.868324
1670 15.798877 -0.013136894 -0.013223946 16.457909 16.526846
2419 25.321693 -0.007444103 -0.007471948 25.482979 25.522523
3081 46.365067  0.006517443  0.006496297 45.244351 45.326908
3343 70.983398  0.050502962  0.049269060 66.259175 66.361778
         ema_diff max_range       atr risk_per_lot
327  -0.012180766  0.266428 0.1891517    0.9457584
576  -0.009539672  0.196785 0.1381247    0.6906237
1670 -0.068936203  0.348215 0.3600002    1.8000009
2419 -0.039543769  0.284999 0.4454686    2.2273431
3081 -0.082557051  0.385002 1.0609378    5.3046891
3343 -0.102603253  3.750000 3.0849998   15.4249991
# Create arrow annotations

# Upper annotations
h_a <- list(
  x = h$Date,
  y = h$slow_ma,
  text = "Sell",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "red",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "left",
  ax = 5,
  ay = -55
)

# Lower annotations

l_a <- list(
  x = l$Date,
  y = l$slow_ma,
  text = "Buy",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 1.5,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 18,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)

Candlestick Chart

Visualizing Moving Average Crossover

A candlestick chart to help visualize what our strategy attempts to do

# colors column for increasing and decreasing
for (i in 2:length(raw_data[,1])) {
  if (raw_data$ema_diff[i] > 0) {
      raw_data$MACD_direction[i] = 'Increasing'
  } else {
      raw_data$MACD_direction[i] = 'Decreasing'
  }
}

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing


# Plot main data
fig <- raw_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)
# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))


# plot MACD line subplot
fig2 <- raw_data 
fig2 <- fig2 %>% plot_ly(x=~Date, y=~ema_diff, type='bar', name = "MACD",
                         color = ~MACD_direction, colors = c("red", "green"), alpha = 0.8)
fig2 <- fig2 %>% layout(yaxis = list(title = "MACD"))


fig = subplot(fig, fig2, heights = c(0.8,0.15), nrows = 2,
              shareX = TRUE, titleY = TRUE)

# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)
fig <- fig %>% layout(annotations = l_a)

# Add title
fig <- fig %>% layout(title = "AAPL Stock - Moving Average Crossover",
                       xaxis = list(rangeslider = list(visible = F)))

fig

Generate signals using MA Indicator

Now we use our indicator to generate a signal

raw_data$signal = NA

# Loop through data to create signal column
for(t in 100:nrow(raw_data)){
  
  current_slow = raw_data$slow_ma[t]
  current_fast = raw_data$fast_ma[t]
  
  prev_slow = raw_data$slow_ma[t-1]
  prev_fast = raw_data$fast_ma[t-1]
  
  # use 'if' statement to translate crossover into signals
  if(current_fast > current_slow & prev_fast < prev_slow) {signal = "B"} else 
    if(current_fast < current_slow & prev_fast > prev_slow) {signal = "S"} else
      {signal = "H"}
  
  raw_data$signal[t] = signal
}

Holding Status and Investment Return

After generating a signal, we must calculate the return from our strategy

In order to calculate switching from short to long simultaneously, we need to calculate two returns and combine them: Say we go buy -> short - \(1+r1\) * \(1+r2\) = \((1+avg(r1,r2))\)

# used to calculate fill price skid

buy_weight = .40
sell_weight = -.40
# Generate a holding status column # 
# and investment return column

raw_data$holding = 0      # first record, you are not holding
raw_data$Inv_return = NA  # obviously the return will also be 0

# we can begin the loop
for (t in 100:nrow(raw_data)){
  
  if(t==1){prev_holding=0} 
  else {prev_holding=raw_data$holding[t-1]}
  
  # look at the signal to decide the change of holding status
  signal = raw_data$signal[t]
  if(signal=="B"){raw_data$holding[t]=1} else 
    if (signal=="S") {raw_data$holding[t]=0} else 
      if (signal=="H"){raw_data$holding[t]=prev_holding}
  
  # Now calculate investment return
  Open = raw_data$Open[t]
  Close = raw_data$Close[t]
  High = raw_data$High[t]
  Low = raw_data$Low[t]
  
  if(prev_holding==0 & signal=="B"){investment_return=Close/((High-Open)*buy_weight+Open) - 1} else 
    if(prev_holding==0 & signal=="H"){investment_return=0} else 
      #if(prev_holding==1 & signal=="S"){investment_return=Close[t-1]/(Open*(1+sell_weight)) - 1} else
      #if(prev_holding==0 & signal=="S"){investment_return=(-1)*(Close/(Open*(1+sell_weight)) - 1)} else
        if(prev_holding==1 & signal=="B"){investment_return=Close/raw_data$Close[t-1]-1} else
          if(prev_holding==1 & signal=="H"){investment_return=Close/raw_data$Close[t-1]-1} else
            {investment_return = Open/raw_data$Close[t-1]-1}
 
   # save the investment return to the dataset
  raw_data$Inv_return[t] = investment_return}

Cumulative Return

initial_wealth = 100000
raw_data$cash = rep(initial_wealth, length(raw_data$Close))
raw_data$n_stock = rep(0, length(raw_data$Close))

for (t in 1:length(raw_data$signal)){
  
  if(t==1){prev_cash = initial_wealth; n_stock = 0
    
    } else
      
      {prev_cash = raw_data$cash[t-1]; 
      n_stock = raw_data$n_stock[t-1]}
  
  signal = raw_data$signal[t]
  Open = raw_data$Open[t]
  Close = raw_data$Close[t]
  High = raw_data$High[t]
  Low = raw_data$Low[t]
  
  #### Note: Added is.na to skip rows at the beginning of the data that do not have a signal
  
  if(is.na(signal)==TRUE){
    prev_cash = raw_data$cash[t-1]
    n_stock = raw_data$n_stock[t-1] 
    
    } else
      
      if (signal=="B"){
        buy_skid = (High-Open) * buy_weight + Open
        trans_prc = buy_skid
        n_change = floor(prev_cash/trans_prc)
        stock_n = n_stock + n_change
        cash = prev_cash - n_change * trans_prc
            
        raw_data$cash[t] = cash
        raw_data$n_stock[t] = stock_n
        
        } else
          
          #### I believe the trouble is with the follow if statement
          ### It seems that the Buy signal works but I am having trouble implementing the sell update
          ## Note: I changed my system so that it does not go short; it can go long only
          
          if (signal =="S"){
            sell_skid = (Open-Low) * sell_weight + Open
            trans_prc = sell_skid
            n_change = n_stock
            stock_n = n_change - n_stock
            cash = prev_cash + n_change * trans_prc
        
            raw_data$cash[t] = cash
            raw_data$n_stock[t] = stock_n
            
            }else
              
              if (signal == "H") {
                raw_data$cash[t] = raw_data$cash[t-1]
                raw_data$n_stock[t] = raw_data$n_stock[t-1]
                
                }
}

Final Return

print(raw_data[max(index(raw_data)),])
           Date   Open   High    Low  Close   Volume  Price
3712 2021-09-29 142.47 144.45 142.03 142.83 74487200 142.83
          return  log_return  slow_ma  fast_ma  ema_diff max_range
3712 0.006482968 0.006462044 137.9734 147.2232 -9.249815  2.539993
          atr risk_per_lot MACD_direction signal holding  Inv_return
3712 3.065625     15.32813     Decreasing      H       1 0.006482968
         cash n_stock
3712 60.70275   16587
final_return = raw_data$n_stock[max(index(raw_data))] * raw_data$Close[max(index(raw_data))] + raw_data$cash[max(index(raw_data))-1]

print(paste("Initial Investment:", sprintf("$ %3.2f", initial_wealth)))
[1] "Initial Investment: $ 100000.00"
print(paste("Final Return:",sprintf("$ %3.2f" , final_return)))
[1] "Final Return: $ 2369181.95"

Max Adverse & Favorable Excursion

Let’s look at the days with the largest gains and losses

max_daily_return = round((max(raw_data$Inv_return, na.rm = TRUE)*100),4)
max_daily_loss =  round((min(raw_data$Inv_return, na.rm = TRUE)*100), 4)
avg_daily_return = round((mean(raw_data$Inv_return, na.rm = TRUE)*100),4)

bh_max_return = round((max(raw_data$return, na.rm = TRUE)*100),4)
bh_max_loss =  round((min(raw_data$return, na.rm = TRUE)*100), 4)
bh_avg_return = round((mean(raw_data$return, na.rm = TRUE)*100),4)

print(paste("Largest 1-day return with trend system:", max_daily_return,"%"))
[1] "Largest 1-day return with trend system: 11.9808 %"
print(paste("Largest 1-day loss with trend system:", max_daily_loss,"%"))
[1] "Largest 1-day loss with trend system: -12.8647 %"
print(paste("Average 1-day with trend system return:", avg_daily_return,"%"))
[1] "Average 1-day with trend system return: 0.1004 %"
print(paste("Largest 1-day return buy and hold strategy:", bh_max_return,"%"))
[1] "Largest 1-day return buy and hold strategy: 13.9049 %"
print(paste("Largest 1-day loss with buy and hold strategy:", bh_max_loss,"%"))
[1] "Largest 1-day loss with buy and hold strategy: -17.9195 %"
print(paste("Average 1-day with buy and hold strategy:", bh_avg_return,"%"))
[1] "Average 1-day with buy and hold strategy: 0.129 %"

Performance Evaluation

Distribution of Returns

p <- raw_data %>%
  ggplot(aes(x=Inv_return)) +
    geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
  coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
  labs(x = "Investment Returns", y = "Frequency of Returns",
       title = "EMA Crossover - Histogram of Investment Returns",
       caption = "Trend following system - Slow lag: 100 periods, Fast lag: 20 periods")+
  theme_classic()
p
bh = raw_data %>%
  ggplot(aes(x=return)) +
    geom_histogram(fill="#69b3a2", color="#e9ecef", alpha=0.7, binwidth = .005) +
  coord_cartesian(xlim = c(-0.07, 0.07), ylim = c(0, 500)) +
  labs(x = "Investment Returns", y = "Frequency of Returns",
       title = "Buy / Hold - Histogram of Investment Returns",
       caption = "This histogram considers a buy and hold strategy")+
  theme_classic()
bh

Mean and Standard Deviation

Provide the average daily return and volatility of the trading system

average_return = mean(raw_data$Inv_return, na.rm = TRUE) # average return
volatility = sd(raw_data$Inv_return, na.rm = TRUE) # volatility

print(paste("Average return:", round(average_return,6)))
[1] "Average return: 0.001004"
print(paste("Volatility:", round(volatility,6)))
[1] "Volatility: 0.015134"
bh_average_return = mean(raw_data$return, na.rm = TRUE) # average return
bh_volatility = sd(raw_data$return, na.rm = TRUE) # volatility

print(paste("Average return:", round(bh_average_return,6)))
[1] "Average return: 0.00129"
print(paste("Volatility:", round(bh_volatility,6)))
[1] "Volatility: 0.020302"
print(paste("Buy and hold strategy witnesses much higher volatility for a small amount of additional return"))
[1] "Buy and hold strategy witnesses much higher volatility for a small amount of additional return"

Sharpe Ratio

Volatility is used to measure the risk

rf = .00001  

print(paste("Trend following: Sharpe ratio of daily returns:",round((average_return-rf)/volatility,5)))
[1] "Trend following: Sharpe ratio of daily returns: 0.0657"
print(paste("Buy & hold: Sharpe ratio of daily returns:",round((bh_average_return-rf)/bh_volatility,5)))
[1] "Buy & hold: Sharpe ratio of daily returns: 0.06304"
print(paste("   Trend following system achieves slightly higher Sharpe ratio"))
[1] "   Trend following system achieves slightly higher Sharpe ratio"

Value at Risk

VAR measures the cut-off return that your financial asset will fall below with certain probability.

var = quantile(raw_data$Inv_return, 0.05, na.rm = TRUE)*100
bh_var = quantile(raw_data$return, 0.05, na.rm = TRUE)*100

print(paste("Trend-following Value at risk; we can expect 95% of the time returns will be greater than:", round(var,4)))
[1] "Trend-following Value at risk; we can expect 95% of the time returns will be greater than: -2.361"
print(paste("Buy / Hold Value at risk; we can expect 95% of the time returns will be greater than:", round(bh_var,4)))
[1] "Buy / Hold Value at risk; we can expect 95% of the time returns will be greater than: -2.9629"

Summary of Investment Return & Market Return

summary(raw_data$Date)
        Min.      1st Qu.       Median         Mean      3rd Qu. 
"2007-01-03" "2010-09-08" "2014-05-17" "2014-05-17" "2018-01-23" 
        Max. 
"2021-09-29" 

Calculate SPX Daily Return

y = raw_data$Inv_return - rf
x = SPX$return - rf
capm = lm(y~x)
summary(capm)

Call:
lm(formula = y ~ x)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.084232 -0.006302 -0.000674  0.006462  0.099507 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.0007829  0.0002192   3.572  0.00036 ***
x           0.5729293  0.0168314  34.039  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.01317 on 3611 degrees of freedom
  (99 observations deleted due to missingness)
Multiple R-squared:  0.2429,    Adjusted R-squared:  0.2427 
F-statistic:  1159 on 1 and 3611 DF,  p-value: < 2.2e-16

This trading \(BETA\) is, the larger the systematic risk is

Beta is the slope (x)

\(Beta\) > 1 indicates an aggressive stock

Beta between 0 and 1 indicates a conservative stock

We use this to measure the aggressiveness of the stock or the trading system

Visualize Days with Largest Loss and Gain

max_gain=raw_data[which.max(raw_data$Inv_return),]
max_loss = raw_data[which.min(raw_data$Inv_return),]

# max(raw_data$Inv_return, na.rm = TRUE)

# Create subset
max_data = raw_data[3310:3330,]
min_data = raw_data[3310:3330,]
max_loss
           Date    Open  High Low   Close    Volume    Price
3323 2020-03-16 60.4875 64.77  60 60.5525 322423600 59.89511
        return log_return  slow_ma  fast_ma  ema_diff max_range
3323 -0.128647 -0.1377081 67.15978 72.53165 -5.371863    9.4925
          atr risk_per_lot MACD_direction signal holding Inv_return
3323 5.212187     26.06094     Decreasing      H       1 -0.1286469
         cash n_stock
3323 28.24028   19665
max_gain
           Date    Open  High     Low   Close    Volume    Price
3322 2020-03-13 66.2225 69.98 63.2375 69.4925 370732000 68.73805
        return log_return  slow_ma  fast_ma  ema_diff max_range
3322 0.1198083  0.1131575 67.24847 73.35779 -6.109322  7.922504
          atr risk_per_lot MACD_direction signal holding Inv_return
3322 4.774375     23.87188     Decreasing      H       1  0.1198083
         cash n_stock
3322 28.24028   19665
# Create arrow annotations

# Upper annotations
h_a <- list(
  x = max_gain$Date,
  y = max_gain$Price,
  text = "Largest Gain",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 2.5,
  opacity = 0.75,
  align = "right",
  ax = 5,
  ay = -55
)

# Lower annotations

l_a <- list(
  x = max_loss$Date,
  y = max_loss$Price,
  text = "Largest Loss",
  xref = "x",
  yref = "y",
  showarrow = TRUE,
  arrowcolor = "green",
  arrowhead = 5,
  arrowsize = 0.8,
  arrowwidth = 2.5,
  opacity = 0.75,
  align = "right",
  ax = -5,
  ay = 55
)


# figure labels
f <- list(
  family = "Courier New, monospace",
  size = 18,
  color = "#7f7f7f ")
x <- list(
  title = "x Axis",
  titlefont = f)
y <- list(
  title = "y Axis",
  titlefont = f)

Largest Gain

fig <- max_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)

# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))

# Add arrow annotations
fig <- fig %>% layout(annotations = h_a)

# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-Day Gain",
                       xaxis = list(rangeslider = list(visible = F)))

fig

Largest Loss

fig <- min_data %>% plot_ly(x = ~Date, type="ohlc",
          open = ~Open, close = ~Close,
          high = ~High, low = ~Low, name = "AAPL",
          increasing = i, decreasing = d)

# color of bars for chart
i <- list(line = list(color = '#17BECF')) # 'i' for increasing
d <- list(line = list(color = '#b22222')) # 'd' for decreasing

# Add Fast and Slow moving average lines
fig <- fig %>% add_lines(x = ~Date, y = ~slow_ma, name = "Slow",
            line = list(color = '#ccc', width = 0.5),
            legendgroup = "Bands", inherit = F,
            showlegend = TRUE, hoverinfo = "none") 
fig <- fig %>% add_lines(x = ~Date, y = ~fast_ma, name = "Fast",
            line = list(color = '#E377C2', width = 0.5),
            hoverinfo = "none", inherit = F)

# Add y-axis title
fig <- fig %>% layout(yaxis = list(title = "Price"))

# Add arrow annotations
fig <- fig %>% layout(annotations = l_a)

# Add title
fig <- fig %>% layout(title = "AAPL Stock - Strategy Largest 1-day Loss",
                       xaxis = list(rangeslider = list(visible = F)))

fig